<?php
/**
* Copyright 2009 Waag Society <http://www.waag.org>
* This file is part of Annotrax.
*
* Annotrax is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* Annotrax is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Annotrax.
* If not, see http://www.gnu.org/licenses/gpl.html
*/

?>
<?php
/**
 * Model for simple database text-search on Movie titles and annotation key-
 * words and comments
 *
 * @todo implement fetch/limit/pager stuff.
 *
 * @author Matthijs van Henten  <Matthijs@waag.org>
 * @license GNU GPL 2.0
 * @verson 0.1
 * @copyright Copyright (c) 2009, Waag Society
 */
class Default_Model_Search{
	const DEFAULT_SEARCH_LIMIT  = 25;
	const DEFAULT_SEARCH_OFFSET = 0;
	const DEFAULT_QUERY_MODE    = 'movie';

    private $limit;
	private $offset;
    private $query;
	private $count;
	private $results;
	private $queryMode;
    private $mapper;

    /**
	 * constructor.
	 * @param array $args Array of arguments to pass to __set for initialization
     */
    public function __construct( array $args = array() ){
		foreach( $args as $key => $value ){
			$this->__set( $key, $value );
		}
    }

	/**
	 * class getter: call a getter if implemented for $name or return the value
	 *
	 * @param string $name of the property
	 * @return mixed $value Value of the property
	 */
    public function __get( $name ){
		if( property_exists( $this, $name ) ){
			if( ( $method = 'get' . ucfirst( $name ) ) && method_exists( $this, $method ) ){
				return call_user_func( array( $this, $method ) );
			}
			else{
				return $this->$name;
			}
		}
    }

	/**
	 * magic setter: call setter method if implemented or set the value
	 *
	 * @return void;
	 */
    public function __set( $name, $value ){
		$name 		= join( '', array_map( 'ucfirst', explode( '_', $name ) ) ); 	//camelize
		$name{0} 	= strtolower( $name{0} );									// camelize

		if( property_exists( $this, $name ) ){
			if( ( $method = 'set' . ucfirst( $name ) ) && method_exists( $this, $method ) ){
				call_user_func( array( $this, $method ), $value );
			}
			else{
				$this->$name = $value;
			}
		}
    }

	/**
	 * Execute the search query
	 *
	 * @param string $mode "movie" or "annotation".
	 * @param string $query Query string
	 *
	 * @return array $results Array of results. Array contains Model_movie or Model_annotation
	 */
	public function search( $mode, $query ){
		$this->clear();
		$this->setQueryMode( $mode );
		$this->setQuery( trim($query) );

		$method = 'search' . ucfirst( $this->getQueryMode() );

		$this->results = call_user_func( array( $this, $method ) );

		return $this->results;
	}

	/**
	 * Execute the search for movies
	 * @return array $results
	 */
	private function searchMovie(){
		return $this->getMapper()->movieSearch( $this );
	}

	/**
	 * Execute the search for annotations
	 * @return array $results
	 */
	private function searchAnnotation(){
		return $this->getMapper()->annotationSearch( $this );
	}

	/**
	 * Set query mode ot 'movie' or 'annotation'
	 * @param $string Either 'movie' or 'annotation'
	 */
	private function setQueryMode( $mode ){
		if( in_array( $mode, array( 'movie', 'annotation' ) ) ){
			$this->queryMode = $mode;
		}
	}

	/**
	 * Returns the queryMode if set, or set the class default and returns that.
	 * @return string $mode
	 */
	public function getQueryMode(){
		if( null == $this->queryMode ){
			$this->queryMode = self::DEFAULT_QUERY_MODE;
		}
		return $this->queryMode;
	}

	/**
	 * Clear all class variable before doing a new query
	 * @return void
	 */
	public function clear(){
		foreach( array('count', 'query', 'queryMode') as $key ){
			$this->$key = null;
		}
	}

	/**
	 * Fetch stored results
	 *
	 * @return array $results Array of resuts from the last query
	 */
	public function getResults(){
		if( null == $this->results ){
			return array();
		}
		return $this->results;
	}

	/**
	 * fetch movies for current search result
	 */
	public function getMovies(){
		// if mode is movies, don't do anything
		$results = $this->getResults();

		if( $this->queryMode == 'movie' ){
			return $results;
		}
		else if( count( $results ) > 0 ){
			$ids = array();
			foreach( $results as $result ){
				$ids[] = $result->movieId;
			}

			return $this->getMapper()->fetchMoviesById(array_filter($ids));
		}
	}


	/**
	 * fetch channels for current search result
	 */
	public function getChannels(){
		// if mode is movies, don't do anything
		$results = $this->getResults();

		if( count( $results ) > 0 ){
			$ids = array();
			if( $this->queryMode == 'movie' ){
				throw new Exception( '@TODO IMPLEMENT');
			}
			else{
				foreach( $results as $result ){
					$ids[] = $result->channelId;
				}
				return $this->getMapper()->fetchChannelsById( array_unique( $ids ) ); //array unique instead of filter
			}
		}

		if( count( $results ) > 0 ){
			$ids = array();
			foreach( $results as $result ){
				$ids[] = $result->movieId;
			}

			return $this->getMapper()->fetchMoviesById( array_filter( $ids ) );
		}
	}

	/**
	 * Get offset if set, or initialize class default
	 * @return int $offset
	 */
	public function getOffset(){
		if( null == $this->offset ){
			$this->offset = self::DEFAULT_SEARCH_OFFSET;
		}
		return $this->offset;
	}

	/**
	 * Return limit if set, or return class default
	 * @return int $limit
	 */
	public function getLimit(){
		if( null === $this->limit ){
			$this->limit = self::DEFAULT_SEARCH_LIMIT;
		}
		return $this->limit;
	}

	/**
	 * Setter for offset
	 * @param int $offset (WILL BE CAST TO INT)
	 */
    public function setOffset( $offset ){
        if( is_numeric( $offset ) ){
            $this->offset = (int) $offset;
        }
        return $this;
    }

	/**
	 * Setter for limit
	 * @param int $limit (WILL BE CAST TO INT)
	 */
    public function setLimit( $limit ){
        if( is_numeric( $limit ) ){
            $this->limit = (int) $limit;
        }
        return $this;
    }

	/**
	 * Set query
	 * @param string $query (will be cast to string)
	 */
    public function setQuery( $query ){
        $this->query = (string) $query;
        return $this;
    }

	/**
	 * do a count query for current $query
	 *
	 * @return int $count
	 */
    public function getCount(){
        if( null == $this->count ){
			$this->count = $this->getMapper()->getCount( $this );
        }

        return $this->count;
    }

	/**
	 * Highlight search results? @todo remove or improve
	 */
    public function highLight( $text, $tagName = 'span', $attributes = null ){
        $attrs = array();

        if( null === $this->query ){
            return $text;
        }

        if( null === $attributes ){
            $attributes = array('class' => 'highlight');
        }

        foreach( $attributes as $key => $value ){
            $attrs[] = $key . '="' . $value . '"';
        }

        $search = $this->query;
        $replace = array();

        foreach( $this->query as $keyword ){
            $replace[] = '<' . $tagName . ' ' . join(' ', $attrs) . '>' . $keyword . '</' . $tagName . '>';
        }

        return str_ireplace( $search, $replace, $text );
    }

    /**
     * Fetch or instantiates the mapper
     * @return object default_model_mapper
     */
    private function getMapper(){
        if( $this->mapper === null ){
            $mapperName = get_class( $this ) . 'Mapper';
            $this->setMapper( new $mapperName() );
        }

        return $this->mapper;
    }

    /**
     * Set mapper
     * @param default_model_mapper $mapper
     */
    private function setMapper( $mapper ){
        $this->mapper = $mapper;
        return $this;
    }
}
